import logging
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Set, List, Union

import networkx as nx
from pycparser import c_ast, parse_file, c_parser, preprocess_file
from pycparser.plyparser import ParseError, Coord

from .baseparser import Parser
from .graphfunc import find_and_merge_cycles
from .parserfunc import get_type_name
from .projectloader import CProjectLoader


@dataclass
class CodeBlock:
    path: Path
    start_line: int
    end_line: int

    def __str__(self):
        return f'{self.path}:{self.start_line}:{self.end_line}'

class EntityCollector:
    def __init__(self, outer):
        self.outer = outer

    def collect(self, node):
        for c in node:
            self.visit(c)

    def visit_Struct(self, node):
        node = node.type
        name = None
        if isinstance(node, c_ast.Struct):
            if node.decls is not None:
                name = f'struct {node.name}'
        elif isinstance(node, c_ast.Union):
            if node.decls is not None:
                name = f'union {node.name}'
        elif isinstance(node, c_ast.Enum):
            if node.values is not None:
                name = f'enum {node.name}'
        if name:
            self.outer.structs[name] = node.coord

    def visit_Typedef(self, node):
        type_decl = node.type
        if isinstance(node.type, c_ast.PtrDecl):
            type_decl = node.type.type

        typ = get_type_name(type_decl)
        if typ == 'unknown type': # 可能是函数指针
            return
        if typ.split(' ')[-1] == 'None': # 匿名声明
            coord = type_decl.type.coord
            str_coord = str(coord)
            if str_coord not in self.outer.struct_anonymous:
                self.outer.struct_anonymous[str_coord] = node.name
                self.outer.structs[node.name] = coord
            else:
                self.outer.typedefs[node.name] = self.outer.struct_anonymous[str_coord]
        else:
            if typ not in self.outer.structs:
                self.visit_Struct(type_decl)
            self.outer.typedefs[node.name] = typ


    def visit(self, node):
        if not self.outer.is_in_project(node.coord):
            return
        if isinstance(node, c_ast.Typedef):
            self.visit_Typedef(node)
        elif isinstance(node, c_ast.Decl):
            self.visit_Decl(node)
        elif isinstance(node, c_ast.FuncDef):
            self.outer.functions[node.decl.name] = node.coord
        else:
            pass

    def visit_Decl(self, node):
        if isinstance(node.type, c_ast.FuncDecl):
            self.outer.functions[node.name] = node.coord
        elif isinstance(node.type, c_ast.TypeDecl):
            self.outer.global_vars[node.name] = node.coord
        elif isinstance(node.type, c_ast.ArrayDecl):
            self.outer.global_vars[node.name] = node.coord
        elif isinstance(node.type, c_ast.PtrDecl):
            self.outer.global_vars[node.name] = node.coord
        else:
            self.visit_Struct(node)

class DependencyVisitor(c_ast.NodeVisitor):
    def __init__(self, outer):
        self.outer = outer
        self.current_function = None

    # 跳过非项目内的结点
    def generic_visit(self, node):
        if self.outer.is_in_project(node.coord) or isinstance(node, c_ast.FileAST):
            super().generic_visit(node)

    def visit_FuncDef(self, node):
        self.current_function = node.decl.name
        if self.current_function not in self.outer.function_calls:
            self.outer.function_calls[self.current_function] = set()
        if self.current_function not in self.outer.struct_uses:
            self.outer.struct_uses[self.current_function] = set()
        if self.current_function not in self.outer.global_var_uses:
            self.outer.global_var_uses[self.current_function] = set()
        self.generic_visit(node)
        self.current_function = None

    def visit_FuncCall(self, node):
        if self.current_function and isinstance(node.name, c_ast.ID):
            func_name = node.name.name
            if func_name in self.outer.functions:
                self.outer.function_calls[self.current_function].add(func_name)
        self.generic_visit(node)

    def visit_ID(self, node):
        if self.current_function:
            if node.name in self.outer.global_vars:
                self.outer.global_var_uses[self.current_function].add(node.name)
        self.generic_visit(node)

    def visit_Struct(self, node):
        if self.current_function:
            self.outer.struct_uses[self.current_function].add(node.name)
        self.generic_visit(node)

    def visit_TypeDecl(self, node):
        if self.current_function:
            typ = get_type_name(node)
            if typ in self.outer.typedefs:
                typ = self.outer.typedefs[typ]
            if typ in self.outer.structs:
                self.outer.struct_uses[self.current_function].add(typ)
        self.generic_visit(node)

class StructDependencyVisitor(c_ast.NodeVisitor):
    def __init__(self, outer):
        self.outer = outer
        self.current_struct = None
        # 控制是否深度搜索，只有在找到声明的时候才打开深度搜索
        self.deep_search = False

    def generic_visit(self, node):
        if self.outer.is_in_project(node.coord) or isinstance(node, c_ast.FileAST):
            super().generic_visit(node)

    def visit_Decl(self, node):
        if self.deep_search:
            self.generic_visit(node)
        elif isinstance(node.type, c_ast.Struct) or isinstance(node.type, c_ast.Union):
            typ = get_type_name(node)
            if typ in self.outer.structs:
                self.current_struct = typ
                self.deep_search = True
                if self.current_struct not in self.outer.struct_dependencies:
                    self.outer.struct_dependencies[self.current_struct] = set()
                self.generic_visit(node)
                self.deep_search = False
                self.current_struct = None

    def visit_Typedef(self, node):
        if self.deep_search:
            # todo: typedef 在局部作用域的时候，但是目前没有考虑
            self.generic_visit(node)
        elif node.name in self.outer.structs:
            self.current_struct = node.name
            self.deep_search = True
            if self.current_struct not in self.outer.struct_dependencies:
                self.outer.struct_dependencies[self.current_struct] = set()
            self.generic_visit(node)
            self.deep_search = False
            self.current_struct = None

    def visit_TypeDecl(self, node):
        if self.current_struct:
            typ = get_type_name(node)
            if typ.split(' ')[-1] == 'None':  # 匿名声明
                typ = node.declname
            if typ in self.outer.typedefs:
                typ = self.outer.typedefs[typ]
            if typ != self.current_struct and typ in self.outer.structs:
                self.outer.struct_dependencies[self.current_struct].add(typ)
        self.generic_visit(node)

class DependencyParser(Parser):
    def __init__(self, project: CProjectLoader, h_file: Union[str, Path, None], c_file: Union[str, Path, None]):
        super().__init__()
        self.logger = logging.getLogger("parser")
        self.origin_graph = None

        self.parser = c_parser.CParser()

        self.project = project
        self.project_path = project.project_path
        self.c_file = None if c_file is None else Path(c_file).resolve()
        self.h_file = None if h_file is None else Path(h_file).resolve()

        # struct 包括 struct, union, enum
        self.struct_anonymous: Dict[str, str] = {}  # anonymous_name -> struct_name
        self.structs: Dict[str, Coord] = {}  # struct_name -> file_location
        self.typedefs: Dict[str, str] = {}  # typedef_name -> original_type
        self.global_vars: Dict[str, Coord] = {}  # var_name -> file_location
        self.functions: Dict[str, Coord] = {}  # func_name -> file_location

        self.function_calls: Dict[str, Set[str]] = {}  # caller -> set of callees
        self.struct_dependencies: Dict[str, Set[str]] = {}  # struct --> set of structs depends
        self.struct_uses: Dict[str, Set[str]] = {}  # func_name -> set of structs used
        self.global_var_uses: Dict[str, Set[str]] = {}  # func_name -> set of globals used

        # 设定fake_libc_include
        import pycparser
        pycparser_path = Path(pycparser.__file__).parent
        self.fake_libc_path = pycparser_path / "utils" / "fake_libc_include"

        # 解析文件
        self.analyze_file()

    def _custom_visual_network(self, net):
        for node_id in net.get_nodes():
            node = net.get_node(node_id)
            if node['extern']:
                node['shape'] = 'diamond'
        return net

    def is_in_project(self, coord: Coord) -> bool:
        if not coord:
            return False
        file_path = Path(coord.file).absolute()
        return file_path.is_relative_to(self.project_path)

    def _collect_include_paths(self) -> List[str]:
        include_paths = set()
        for file in filter(lambda p: p.suffix == '.h', self.project.get_all_files()):
            include_paths.add(file.parent)
        return list(include_paths)

    def analyze_location(self, coord: Coord) -> str:
        end_line = self.project.find_block_end(coord.file, coord.line)
        if end_line is None:
            end_line = coord.line
        block = CodeBlock(Path(coord.file).resolve(), coord.line, end_line)
        return str(block)

    def is_extern_node(self, coord: Coord) -> bool:
        filepath = Path(coord.file)
        if self.c_file is not None and filepath.resolve() == self.c_file.resolve():
            return False
        if self.h_file is not None and filepath.resolve() == self.h_file.resolve():
            return False
        return True

    @staticmethod
    def preprocess_cpp_content(content):
        # 替换 static_cast
        content = re.sub(
            r'(.*?)\(static_cast<bool>\((.*?)\)\s*\?\s*void\(0\)\s*:\s*__assert_fail\((.*?)\)\);',
            r'\1if (!\2) { __assert_fail(\3); }',
            content
        )
        return content

    def analyze_file(self):
        self.graph = nx.DiGraph()

        # 设置预处理器参数
        include_paths = self._collect_include_paths()
        cpp_args = [
            '-E',
            '-xc',
            '-DALLOC_TESTING',
            f'-I{self.fake_libc_path}']
        cpp_args.extend([f'-I{path}' for path in include_paths])
        cpp_args.extend([
            # '-D__attribute__(x)=',
            '-D__extension__=',   #  要处理gcc的特殊语法
            # '-D__inline__=inline',
            # '-D__ASSERT_FUNCTION=__func__'
            # '-Dstatic_cast<(x)>=(x)',
        ])

        target_file = self.c_file
        if self.c_file is None:
            target_file = self.h_file

        try:
            preprocessed_file = preprocess_file(target_file,
                                                cpp_path='gcc',
                                                cpp_args=cpp_args)
            if target_file.suffix == '.cpp':
                preprocessed_file = self.preprocess_cpp_content(preprocessed_file)

            parser = c_parser.CParser()
            ast = parser.parse(preprocessed_file, target_file)
            # 解析文件
            # ast = parse_file(
            #     target_file,
            #     use_cpp=True,
            #     cpp_args=cpp_args,
            #     cpp_path='gcc'
            # )
            # 访问AST收集依赖信息
            visitor = EntityCollector(self)
            visitor.collect(ast)

            # 搜索结构体依赖关系
            visitor = StructDependencyVisitor(self)
            visitor.visit(ast)

            # 搜索依赖关系
            visitor = DependencyVisitor(self)
            visitor.visit(ast)
        except ParseError as e:
            self.logger.error(f"Error parsing file {target_file}: {str(e)}")
            # todo 可以直接异常
            return

        # 构建依赖图
        # 添加函数节点和调用边
        for function_name, location in self.functions.items():
            self.graph.add_node(function_name, type='function',
                                extern=self.is_extern_node(location),
                                location=self.analyze_location(location))
        for caller, callees in self.function_calls.items():
            for callee in callees:
                if caller in self.functions and callee in self.functions:
                    self.graph.add_edge(caller, callee, type='function_call')

        # 添加结构体节点和使用边
        for struct_name, location in self.structs.items():
            self.graph.add_node(struct_name, type='struct',
                                extern=self.is_extern_node(location),
                                location=self.analyze_location(location))
        for struct_name, depend_structs in self.struct_dependencies.items():
            for depend_struct in depend_structs:
                if struct_name in self.structs and depend_struct in self.structs:
                    self.graph.add_edge(struct_name, depend_struct, type='struct_dependency')
        for function_name, used_structs in self.struct_uses.items():
            for used_struct in used_structs:
                if function_name in self.functions and used_struct in self.structs:
                    self.graph.add_edge(function_name, used_struct, type='struct_use')

        # 添加全局变量节点和使用边
        for var_name, location in self.global_vars.items():
            self.graph.add_node(var_name, type='global',
                                extern=self.is_extern_node(location),
                                location=self.analyze_location(location))
        for function_name, used_vars in self.global_var_uses.items():
            for used_var in used_vars:
                if function_name in self.functions and used_var in self.global_vars:
                    self.graph.add_edge(function_name, used_var, type='global_use')

        # 删除外部孤立节点
        remove_nodes = filter(lambda n: self.graph.nodes[n]['extern'], nx.isolates(self.graph))
        self.graph.remove_nodes_from(list(remove_nodes))

        # 删除自循环
        self.graph.remove_edges_from(nx.selfloop_edges(self.graph))

        self.origin_graph = self.graph.copy()

        # 合并循环图
        self.graph = find_and_merge_cycles(self.graph, self._merge_node)

    @staticmethod
    def _merge_node(graph: nx.DiGraph, scc):
        new_node = ', '.join(sorted(str(n) for n in scc))
        new_node = f'({new_node})'
        nodes_data = []
        for node_id in scc:
            node_data = graph.nodes[node_id]
            node_data['name'] = node_id
            nodes_data.append(node_data)
        new_node_data = {
            'type': f'{nodes_data[0]['type']}_group',
            'extern': list(map(lambda d: d['extern'], nodes_data)),
            'name': list(map(lambda d: d['name'], nodes_data)),
            'location': list(map(lambda d: d['location'], nodes_data)),
        }
        return new_node, new_node_data